<?php
/**************************************************************************
 ESAM (EVE to SMF API Module)
 Copyright (C) 2009  Clinton Weiss

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 **************************************************************************/

$commandLineExecution = "cli" == php_sapi_name();
if (!defined('SMF'))
{
	require(dirname(__FILE__) . '/../SSI.php');
}

$debug_level = ( isset($_GET['verbosity']) ? $_GET['verbosity'] : 0);
if ($debug_level != 0)
{
	isAllowedTo(array('admin_forum'));
}

function debug($verbosity, $message)
{
	global $debug_level;
	if ($debug_level != 0 && $verbosity <= $debug_level)
	{
		print $message;
	}
}

include_once "EsamAlliance.php";
include_once "EsamMembers.php";
include_once "EsamQuery.php";
include_once "EsamApi.php";
include_once "EsamGlobal.php";
include_once "EsamRules.php";
include_once "EsamSkillDump.php";

$file = $_SERVER["PHP_SELF"];
$fileLength = strlen($file);
$wantedLength = strlen("EsamValidator.php");
if ($fileLength > $wantedLength )
{
	$file = substr($file, $fileLength - $wantedLength, $fileLength);
}
if ($commandLineExecution || ($file == "EsamValidator.php" && isset($_GET['cronjob'])))
{
	$esamValidator = new EsamValidator();
	$esamValidator->validateApiKeys();
}

class EsamValidator
{
	private $esamGlobal = null;

	private $includeCorpTicker = false;

	private $alliance_xml = null;

	function getAllianceXml()
	{
		if (empty($this->alliance_xml))
		{
			$ea = new EsamApi();
			$this->alliance_xml =  $ea->getAllianceList();
		}
		return $this->alliance_xml;
	}

	function changeAvatar($memId, $toonId)
	{
		global $modSettings, $txt;
		require_once("Subs-Graphics.php");
		debug(1, "<br/>" . $txt['eve_portrait'] . "<br/>");
		downloadAvatar("http://img.eve.is/serv.asp?s=256&c=" . $toonId, $memId, $modSettings['avatar_max_width_upload'], $modSettings['avatar_max_height_upload']);
	}


	function setVerbosity($verbose_level)
	{
		global $debug_level;
		$debug_level = $verbose_level;
	}

	function isApiOnline()
	{
		$ea = new EsamApi();
		return $ea->isApiOnline();
	}

	/**
	 * Validates all stored API keys
	 */
	// TODO: break this apart into smaller methods
	function validateApiKeys($validate_member_id = null)
	{
		global $scripturl, $txt, $commandLineExecution;

		// Check that the API servers are online and Tranquility is operational
		if (! $this->isApiOnline())
		{
			return;
		}

		$ea = new EsamApi();

		$startTime = $this->getTime();
		$multipleKeyAuthentication = $validate_member_id == null;

		$resultSet = null;
		if (empty($validate_member_id))
		{
			$esamQuery = new EsamQuery();
			$esamQuery->setQuery($esamQuery->Q_EVE_API_LIST);
			$resultSet = $esamQuery->getResultSet();
		} else
		{
			$esamQuery = new EsamQuery();
			$esamQuery->setQuery($esamQuery->Q_EVE_API);
			$esamQuery->setParameter("member_id", $validate_member_id);
			$resultSet = $esamQuery->getResultSet();
		}

		// Prepare Global
		$this->esamGlobal = new EsamGlobal();
		$this->includeCorpTicker = $this->esamGlobal->getGlobalSetting("ADD_CORP_TICKER", "false") == "true";

		// Prepare Members
		$esamMembers = new EsamMembers();
		// Prepare Alliance
		$esamAlliance = new EsamAlliance();

		// Load the rules
		$esamRules = new EsamRules();
		$rules = $esamRules->listRules();

		if (sizeof($resultSet) == 0)
		{
			debug(0, $txt['eve_no_apis_to_validate'] . " <br />");
		}
		if (sizeof($rules) == 0)
		{
			debug(0, $txt['eve_no_rules_to_validate'] . " <br />");
		}

		// Get a full list of all the MemberGroups that have
		// a rule defined for them
		$memberGroupsWithRules = array();
		foreach ($rules as $row)
		{
			$memberGroupsWithRules[] = $row['GROUP_ID'];
		}
		$memberGroupsWithRules = array_unique($memberGroupsWithRules);

		$startValidationWith = $this->esamGlobal->getGlobalSetting("START_VALIDATION_WITH", -1);

		$validateCount = 0;
		$totalCount = 0;
		foreach ($resultSet as $row)
		{
			$member_id = $row['ID_MEMBER'];
			$user_id = $row['USER_ID'];
			$api_key = $row['API_KEY'];
			$valid_api_key = $row['VALID'];

			if ($multipleKeyAuthentication && !empty($valid_api_key) && $valid_api_key == "N") continue;

			$currentTime = $this->getTime();
			if ($multipleKeyAuthentication && ($currentTime - $startTime) > 20)
			{
				// We're taking too long, let the next iteration handle the rest
				$this->esamGlobal->setGlobalSetting("START_VALIDATION_WITH", $member_id);
				debug(1, $txt['eve_validation_timeout_1'] . "<br />");
				debug(1, "<a href='$scripturl?action=EsamSettings;sa=validate'>" . $txt['eve_validation_timeout_2'] . "</a><br />");
				debug(1, $txt['eve_validation_timeout_3']);
				debug(1, "<script>setTimeout('window.location = \'$scripturl?action=EsamSettings;sa=validate\';', 30000);</script>");

				if ($commandLineExecution)
				{
					echo "ESAM: Validated " . $validateCount . " of " . $totalCount . "\n";
				}

				return;
			}
			else if ($multipleKeyAuthentication && $member_id < $startValidationWith)
			{
				continue;
			}

			$totalCount++;

			$valid = $this->checkUserAndKey($user_id, $api_key);

			// Convert $valid into true or false
			$valid = $valid ? "true" : "false";
			debug(1, $txt['eve_member'] . ": <b>" . $esamMembers->getMemberName($member_id) . "</b><br />");
			debug(2, $user_id . ' ' . $api_key . ' ' . $valid . '<br />');

			// check to see if the user is still in the DB
			$esamQuery = new EsamQuery();
			$esamQuery->setQuery($esamQuery->Q_MEMBER_INFO);
			$esamQuery->setParameter("member_id", $member_id);
			$memberInfo = $esamQuery->getResultSet();

			if (sizeof($memberInfo) == 0 || $valid == "false")
			{
				// Clear member from MemberGroups that have rules
				$esamMembers->clearMemberFromMemberGroups($member_id, $memberGroupsWithRules);

				// I've seen cases where API issues will mark a user invalid.  Allow up
				// to four attempts for a user to be marked invalid (gives time for a user
				// to reactivate their account too).
				$new_validity = "";
				switch ($valid_api_key) {
					case "A":
						$new_validity = "B";
						break;
					case "B":
						$new_validity = "C";
						break;
					case "C":
						$new_validity = "D";
						break;
					case "D":
						$new_validity = "N";
						break;
					default:
						$new_validity = "A";
				}

				// Mark the API as invalid
				$esamQuery = new EsamQuery();
				$esamQuery->setQuery($esamQuery->U_APIKEY_VALID);
				$esamQuery->setParameter("member_id", $member_id);
				$esamQuery->setParameter("valid", $new_validity);
				$esamQuery->executeQuery();
			}
			else
			{
				// Clear member from MemberGroups that have rules
				$esamMembers->clearMemberFromMemberGroups($member_id, $memberGroupsWithRules);

				// Mark the API as Valid but doesn't match any rules (yet)
				$esamQuery = new EsamQuery();
				$esamQuery->setQuery($esamQuery->U_APIKEY_VALID);
				$esamQuery->setParameter("member_id", $member_id);
				$esamQuery->setParameter("valid", "V");
				$esamQuery->executeQuery();

				$ea = new EsamApi();
				$xml = $ea->getCharacters($user_id, $api_key);
				debug(2, "<pre>" . str_replace(">", "&gt;", str_replace("<", "&lt;", $xml->asXML())) . "</pre>");

				$toon_names = array();

				foreach ($xml->result->rowset->row as $row)
				{
					foreach ($row->attributes() as $key => $val)
					{
						if($key == 'characterID')
						{
							$cxml = $ea->getCharacterSheet($user_id, $api_key, $val);
							if($ea->isApiError())
							{
								debug(1, '<p style="color: red;">ERROR:<br />');
								debug(1, $ea->getErrors());
								debug(1, '</p>');
								debug(3, "<a href='http://api.eve-online.com/char/CharacterSheet.xml.aspx?userID=" . $user_id . "&apiKey=" . $api_key. "&characterID=" . $val . "'>" . $txt['eve_char_sheet_link'] . "</a><br />");
								debug(1, '<br /><br />');
							}
							else
							{
								$char_name = $cxml->result->name;
								$corp_id = $cxml->result->corporationID;
								$corp_name = $cxml->result->corporationName;
								$char_id = $cxml->result->characterID;

								$alliance_id = 0;
								try {
									$alliance_id = $cxml->result->allianceID;
									$alliance_name = (string)  $cxml->result->allianceName;
								} catch (Exception $aex) {
									$alliance_id = 0;
								}
								if ($alliance_id == 0 ) $alliance_name = "None";

								$charIsDirector = false;
								$cresult = $cxml->xpath("//eveapi/result/rowset[@name='corporationRoles']");
								foreach ($cresult[0]->row as $crow)
								{
									foreach ($crow->attributes() as $ckey => $cval)
									{
										if($ckey == 'roleName' && $cval == 'roleDirector')
										{
											$charIsDirector = true;
										}
									}
								}
								$titles = array();
								$tresult = $cxml->xpath("//eveapi/result/rowset[@name='corporationTitles']");
								foreach ($tresult[0]->row as $trow){
									foreach ($trow->attributes() as $tkey => $tval){
										if ($tkey == 'titleName'){
											$titles[] = $tval;
										}
									}
								}

								debug(1, 'Character: <b>' . $char_name . '</b><br />');
								debug(1, $txt['eve_corporation'] . ': ' . $corp_id . ' - ' . $corp_name . '<br />');
								debug(1, $txt['eve_alliance'] . ': ' . $alliance_id . ' - ' . $alliance_name . '<br />');
								debug(1, 'isDirector? ' . ($charIsDirector ? 'yes' : 'no') . '<br />');
								debug(1, 'Titles: ' . implode(", ", $titles) .  '<br />');
								debug(3, "<a href='http://api.eve-online.com/char/CharacterSheet.xml.aspx?userID=" . $user_id . "&apiKey=" . $api_key. "&characterID=" . $char_id . "'>" . $txt['eve_char_sheet_link'] . "</a><br />");

								debug(2, "<pre>" . str_replace(">", "&gt;", str_replace("<", "&lt;", $cxml->asXML())) . "</pre>");

								// Iterate through all rules
								foreach ($rules as $rule)
								{
									$rule_type = $rule['TYPE'];
									$rule_requires_director_role = ($rule['ROLE'] == 'roleDirector');
									$rule_role_id = $rule['ROLE_ID'];
									$rule_title = $rule['TITLE'];
									$rule_requires_title = ($rule['TITLE'] != "" && $rule['TITLE'] != "null");

									$member_group = $rule['GROUP_ID']; // Member Group
									$member_group_name = $this->esamGlobal->getGroupName($member_group);
									$skillTypeId = $rule['SKILL_ID'];
									$skillLvl = $rule['SKILL_LVL'];

									$skillRequired = false;
									$hasSkill = false;
									$skillDisplay = "";
									if (!empty($skillTypeId) && !empty($skillLvl))
									{
										$skillRequired = true;
										$skills = $cxml->result->rowset[0];
										foreach ($skills as $skill)
										{
											if ( $skill["typeID"] == $skillTypeId && (int) $skill["level"] >= (int) $skillLvl)
											{
												$skillsArray = getSkillArray();
												$skillDisplay = " " . $txt['eve_has_skill'] . " " . $skillsArray["$skillTypeId"] . " " . $txt['eve_trained_skill'] . " " . $skillLvl;
												$hasSkill = true;
											}
										}
									}

									$title_display = "";
									if ($rule_requires_title)
									{
										$title_display = " and has title " . $rule_title;
									}

									if (!$skillRequired || ($skillRequired && $hasSkill))
									{
										switch($rule_type)
										{
											case 'P': // Pilot rules
												if ((string) $char_id == (string) $rule_role_id)
												{
													$this->addMemberToGroup($member_id, $member_group);
													$toon_names = $this->addUnique($toon_names, $char_name, $char_id, $corp_id);
													debug(1, $txt['eve_is_pilot'] . " " . $esamAlliance->getCharacterName($rule_role_id));
													debug(1, $txt['eve_member_qualifies'] . " " . $member_group_name . " <br />" . $skillDisplay);
												}
												break;
											case 'A': // Alliance rules
												$rule_alliance_id = $rule_role_id; // only here for clarification
												$isMemberCorp = $rule_alliance_id == $alliance_id;

												if ($isMemberCorp && !$rule_requires_director_role)
												{
													$this->addMemberToGroup($member_id, $member_group);
													$toon_names = $this->addUnique($toon_names, $char_name, $char_id, $corp_id);
													debug(1,  $txt['eve_is'] . " " . ( $rule_requires_director_role ? "<i>" . $txt['eve_director'] . "</i> " . $txt['eve_within_corp_of'] . " " : "<i>" . $txt['eve_member'] . "</i> " . $txt['eve_of_corp_of'] . " ") . " " . $txt['eve_alliance'] . ": <i>" . $alliance_name . "</i>" . $skillDisplay);
													debug(1, $txt['eve_member_qualifies'] . $member_group_name . " <br />");
												}
												else if ($isMemberCorp && $rule_requires_director_role && $charIsDirector)
												{
													$this->addMemberToGroup($member_id, $member_group);
													$toon_names = $this->addUnique($toon_names, $char_name, $char_id, $corp_id);
													debug(1,  $txt['eve_is'] . " " . ( $rule_requires_director_role ? "<i>" . $txt['eve_director'] . "</i> " . $txt['eve_within_corp_of'] . " " : "<i>" . $txt['eve_member'] . "</i> " . $txt['eve_of_corp_of'] . " ") . " " . $txt['eve_alliance'] . ": <i>" . $alliance_name . "</i>" . $skillDisplay);
													debug(1, $txt['eve_member_qualifies'] . $member_group_name . " <br />");
												}

												break;
											case 'C': // Corporation rules
												$corporationName = $esamAlliance->getCorporationName($rule_role_id);
												$rule_corp_id = $rule_role_id; // only here for clarification
												if ($corp_id == $rule_corp_id && ( $charIsDirector || $charIsDirector == $rule_requires_director_role ))
												{
													if ($rule_requires_title && !in_array($rule_title, $titles))
													{
														break;
													}

													$this->addMemberToGroup($member_id, $member_group);
													$toon_names = $this->addUnique($toon_names, $char_name, $char_id, $corp_id);
													debug(1, $txt['eve_is'] . " " . ( $rule_requires_director_role ? "<i>" . $txt['eve_director'] . "</i> " . $txt['eve_within_corp_of'] . " " : "<i>" . $txt['eve_member'] . "</i> " . $txt['eve_of_corp_of'] . " ") . " " . $txt['eve_alliance'] . ": <i>" . $corporationName . "</i>" . $title_display . $skillDisplay);
													debug(1, $txt['eve_member_qualifies'] . $member_group_name . " <br />");
												}
												break;
											case 'F': // Facton Warfare
												try
												{
													$fwStats = $ea->getFactionWarfareStats($user_id, $api_key, $char_id);
													$factionId = $fwStats->xpath("//eveapi/result/factionID");
													$factionId = $factionId[0];
													if ($factionId == $rule_role_id && ( $charIsDirector || $charIsDirector == $rule_requires_director_role ) )
													{
														$this->addMemberToGroup($member_id, $member_group);
														$toon_names = $this->addUnique($toon_names, $char_name, $char_id, $corp_id);
														$factionName = $esamAlliance->getFactionWarfareName($rule_role_id);
														debug(1, $txt['eve_is'] . " " . ( $rule_requires_director_role ? "<i>" . $txt['eve_director'] . "</i> " . $txt['eve_within_faction_of'] . " " : "<i>" . $txt['eve_member'] . "</i> " . $txt['eve_of_faction_of'] . " ") . ": <i>" . $factionName . "</i>" . $skillDisplay);
														debug(1, $txt['eve_member_qualifies'] . $member_group_name . " <br />");
													}
													break;
												} catch (Exception $factionException)
												{
													// not a faction member, move on!
												}
										}
									}
								}

								debug(1, "<br />");
							}
						}
					}
				}

				// If toon_names > 0, the API is valid and matches rules.
				if ( sizeof($toon_names) > 0 )
				{
					// Mark the API as Valid and matches rules
					$esamQuery = new EsamQuery();
					$esamQuery->setQuery($esamQuery->U_APIKEY_VALID);
					$esamQuery->setParameter("member_id", $member_id);
					$esamQuery->setParameter("valid", "Y");
					$esamQuery->executeQuery();
				}

				$preferredToonId = 0;
				if ( $this->esamGlobal->getGlobalSetting('DISPLAY_EVE_NAME', "false") == "true")
				{
					// Update the display name
					$name = "";
					if (sizeof($toon_names) > 0 )
					{
						$usersPreferredChar = $esamMembers->getMemberSetting($member_id, "PREFERRED_CHAR_ID", "all");

						$first = true;
						foreach (array_keys($toon_names) as $toon_id)
						{
							$currentName = $toon_names[$toon_id];
							if ($usersPreferredChar == "all" || (string) $toon_id == (string) $usersPreferredChar)
							{
								if ($first == true || (string) $toon_id == (string) $usersPreferredChar)
								{
									$preferredToonId = $toon_id;
								}

								if (!$first)
								{
									$name = $name . ", ";
								}
								$name = $name . $currentName;

								$first = false;
							}
						}
					}
					if ($name == "")
					{
						$name = $esamMembers->getMemberName($member_id);
					}
					debug(1, $txt['eve_assigning_name'] . ": <b>" . $name . "</b>");

					if ( $this->esamGlobal->getGlobalSetting('DISPLAY_EVE_PORTRAIT', "false") == "true")
					{
						if ($preferredToonId != 0 && !$this->hasPortrait($member_id))
						{
							$this->changeAvatar($member_id, $preferredToonId);
						}
					}

					// Assign custom titles
					if ( $preferredToonId != 0 && $this->esamGlobal->getGlobalSetting('DISPLAY_CORP_TITLES', "false") == "true")
					{
						$first = true;
						$custom_title = "";
						$cxml = $ea->getCharacterSheet($user_id, $api_key, $preferredToonId);
						$cresult = $cxml->xpath("//eveapi/result/rowset[@name='corporationTitles']");
						foreach ($cresult[0]->row as $crow)
						{
							foreach ($crow->attributes() as $ckey => $cval)
							{
								if ( $ckey == 'titleName')
								{
									if ( !$first ) $custom_title = $custom_title . ", ";
									$first = false;
									$custom_title = $custom_title . " " . $cval;
								}
							}
						}
						debug(1, "<br>Custom Title: " . $custom_title);
						$esamQuery = new EsamQuery();
						$esamQuery->setQuery($esamQuery->U_MEMBER_CUST_TITLE);
						$esamQuery->setParameter("member_id", $member_id);
						$esamQuery->setParameter("custom_title", mysql_escape_string($custom_title . " "));
						$esamQuery->executeQuery();
					}


					$esamQuery = new EsamQuery();
					$esamQuery->setQuery($esamQuery->U_MEMBER_NAME);
					$esamQuery->setParameter("member_id", $member_id);
					$esamQuery->setParameter("real_name", mysql_escape_string($name));
					$esamQuery->executeQuery();
					$validateCount++;
				}
			}
			debug(1, "<hr /><br />");
		}
		if ($multipleKeyAuthentication)
		{
			$this->esamGlobal->removeGlobalSetting("START_VALIDATION_WITH");
		}
		debug(1, "<h3>" . $txt['eve_validation_complete'] . "</h3>");

		if ($commandLineExecution)
		{
			echo "ESAM: Validated " . $validateCount . " of " . $totalCount . "\n";
		}
	}

	function getTime()
	{
		$date = getdate();
		return $date[0];
	}

	function addMemberToGroup($member_id, $member_group)
	{
		// (-1, 0, 1, 3)
		if (!array_search($member_group, array(-1, 0, 1, 3)))
		{
			$esamMembers = new EsamMembers();
			$esamMembers->addMemberToMemberGroup($member_id, $member_group);
		}
	}

	/**
	 * Created method since array_unique() was not working properly.
	 *
	 * @param <type> $array
	 * @param <type> $value
	 * @return <type>
	 */
	function addUnique($array, $name, $char_id, $corp_id)
	{
		if ($this->includeCorpTicker)
		{
			$eveApiCorp = new EsamApi();
			$corpInformationXml = $eveApiCorp->getCorporationInfo($corp_id);
			$corpTicker = (string) $corpInformationXml->result->ticker;
			$name = "[" . $corpTicker . "] " . $name;
		}

		if (empty($array[(string) $char_id]))
		{
			$array[(String) $char_id] = $name;
		}
		return $array;
	}

	/**
	 * Checks a particular member id for valid API
	 *
	 * @param <type> $member_id
	 * @return <type>
	 *
	 */
	function checkMember($member_id)
	{
		$esamQuery = new EsamQuery();
		$esamQuery->setQuery($esamQuery->Q_EVE_API);
		$esamQuery->setParameter("member_id", $member_id);
		$resultSet = $esamQuery->getResultSet();

		if (sizeof($resultSet) == 0 )
		{
			return "false";
		}

		$row = $resultSet[0];
		$user_id = $row['USER_ID'];
		$api_key = $row['API_KEY'];

		return $this->checkUserAndKey($user_id, $api_key) ? "true" : "false";
	}

	/**
	 * Checks that the user_id and api key are valid.
	 * Returns true if it is, false otherwise.
	 *
	 * @param <type> $user_id
	 * @param <type> $api_key
	 * @return <type>
	 */
	function checkUserAndKey($user_id, $api_key)
	{
		// check user info
		$ea = new EsamApi();
		try {
			$data = $ea->getCharacters($user_id, $api_key);
			return !$ea->isApiError();
		} catch (Exception $ex) {
			return false;
		}
	}

	/**
	 * Returns true if member already has a portrait assigned, false otherwise.
	 *
	 * @param <type> $memberId
	 */
	function hasPortrait($memberId)
	{
		$esamQuery = new EsamQuery();
		$esamQuery->setQuery($esamQuery->Q_PORTRAIT_COUNT);
		$esamQuery->setParameter("member_id", $memberId);
		$result = $esamQuery->getResultSetValue("count");
		return ! ((string) $result == '0');
	}

	function isValidApiQuickCheck($memberId)
	{
		$esamQuery = new EsamQuery();
		$esamQuery->setQuery($esamQuery->Q_EVE_API);
		$esamQuery->setParameter("member_id", $memberId);
		$result = $esamQuery->getResultSetValue("VALID");
		return $result != "N";
	}
}
?>
